package com.xiyuan.smartutils.template.engine.statement;

import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.constants.CodeConstants;
import com.xiyuan.smartutils.crypto.MD5;
import com.xiyuan.smartutils.template.engine.ExpressionParser;
import com.xiyuan.smartutils.template.engine.StatementParser;
import com.xiyuan.smartutils.template.engine.TagType;
import com.xiyuan.smartutils.template.engine.VariableMap;
import com.xiyuan.smartutils.template.engine.exception.ExpressionException;
import com.xiyuan.smartutils.template.engine.exception.StatementException;
import com.xiyuan.smartutils.template.engine.expression.Expression;
import com.xiyuan.smartutils.template.engine.statement._Break.BreakException;
import com.xiyuan.smartutils.template.engine.statement._Continue.ContinueException;
import com.xiyuan.smartutils.template.engine.statement._RETURN.ReturnException;

import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

/***
 * 1、嵌套语句
 *
 * @see _IF 判断语句，格式为：<#if a=1>内容1<#elseif a==2>内容2<#elseif a==3>内容3<#else>其他内容<#if>
 * @see _FOR 迭代语句，格式为：<#for item : list></#for>
 * @see _FUNCTION 全局函数定义语句，格式为：<#function name(param1, param2)></#function>
 * <br>
 * 2、非嵌套语句
 * @see _Text 纯文本语句
 * @see _Var 变量定义语句，格式为：<#var i=1/>
 * @see _Echo 表达式语句，格式为：${obj.getName()}
 * @see _RETURN 返回语句,作用在 _Function 里格式为<#function name(param1, param2)> <#return "ssss"/> </#function>
 * @see _INCLUDE 包含语句，本身不嵌套，调用后转到父ML进行嵌套处理，格式为<#include "/zview/include.page"/>
 * @version v1.0.0 @author lgz 2020年10月12日 新建与整理
 */
public abstract class Statement implements CodeConstants, Serializable {
    private static final long serialVersionUID = 1L;
    
    // 标签名称定义
    public static final TagType TEXT = TagType.TEXT;
    public static final TagType ECHO = TagType.ECHO;
    public static final TagType ECHO_FORMAT = TagType.ECHO_FORMAT;
    public static final TagType CALL = TagType.CALL;
    public static final TagType COMMENT = TagType.COMMENT;
    public static final TagType INCLUDE = TagType.INCLUDE;
    public static final TagType FUNCTION = TagType.FUNCTION;
    public static final TagType RETURN = TagType.RETURN;
    public static final TagType VAR = TagType.VAR;
    public static final TagType IF = TagType.IF;
    public static final TagType ELSEIF = TagType.ELSEIF;
    public static final TagType ELSE = TagType.ELSE;
    public static final TagType FOR = TagType.FOR;
    public static final TagType BREAK = TagType.BREAK;
    public static final TagType CONTINUE = TagType.CONTINUE;
    public static final TagType END_IF = TagType.END_IF;
    public static final TagType END_FUNCTION = TagType.FUNCTION_END;
    public static final TagType END_FOR = TagType.FOR_END;
    
    // 标签属性
    protected List<Expression> expressions;// 运算表达式
    protected LinkedHashSet<Statement> childs;// 子集标签列表
    
    private StatementParser parser;
    // private String name;// 一句名称，名称组成，type_值表示
    private String path;// 捆绑的资源文件
    private Statement parent;
    private int lineNo = -1;// 开始定义的行号
    private int offlineNo = -1;// 定义结束标签符的行号
    private int begin_column = 0;// 文档开始索引
    private String statement;// 源字符串表达式
    private boolean isCompleteTag = false;// 开始标签完成
    
    Statement(StatementParser parser) {
        this.parser = parser;
        this.path = parser.getPath();
        this.begin_column = parser.getColumn();
    }
    
    public String getPath() {
        return path;
    }
    
    public int getBeginColumn() {
        return begin_column;
    }
    
    public final Statement getParent() {
        return parent;
    }
    
    protected void setParent(Statement parent) {
        this.parent = parent;
    }
    
    public void addStatement(Statement stmt) {
        if (stmt == null) {return;}
        
        stmt.setParent(this);
        if (childs == null) childs = new LinkedHashSet<Statement>();
        
        childs.add(stmt);
    }
    
    /**
     * 知否有子集标签
     */
    public boolean hasChild() {
        return childs != null && !childs.isEmpty();
    }
    
    /**
     * 获取语句代码
     */
    public final String getStatement() {
        return statement;
    }
    
    public final Statement setStatement(String statement) {
        this.statement = statement;
        
        if (this.lineNo != -1) {return this;}
        
        int s = -1;
        for (int i = 0; i < statement.length(); i++) {
            char ch = statement.charAt(i);
            if (s == -1 && !Validates.isWhitespace(ch)) {s = 0;}
            if (s != -1 && ch == '\n') {s++;}
        }
        
        this.lineNo = parser.getLineNum() - s;
        this.offlineNo = this.lineNo;
        this.hashCode();
        if(this instanceof _Text && StrUtils.isBlank(statement)){
            
        }
        return this;
    }
    
    private int hash = 0;
    
    private String hashStr() {
        return (parser.getPath() + ":" + this.lineNo + "-" + this.offlineNo + ":" + MD5.encodeUTF8(this.statement));
    }
    
    @Override
    public int hashCode() {
        if (hash == 0) hash = hashStr().hashCode();
        return hash;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o == null) {return false;}
        
        if (!(o instanceof Statement)) {return false;}
        
        String hashStr1 = ((Statement) o).hashStr();
        String hashStr2 = this.hashStr();
        return hashStr1.equals(hashStr2);
    }
    
    public int getLineNo() {
        return lineNo;
    }
    
    public int getOfflineNo() {
        return offlineNo;
    }
    
    public void setCompleteTag() {
        this.isCompleteTag = true;
        if (parser != null) {this.offlineNo = parser.getLineNum();}
    }
    
    /**
     * 标签是否定义完成
     */
    public boolean isCompleteTag() {
        return isCompleteTag;
    }
    
    @Override
    public String toString() {
        return this.statement;
    }
    
    // 子类必须实现的方法
    
    /**
     * 构建标签内容
     */
    protected abstract void build(VariableMap variableMap, PrintWriter out);
    
    /**
     * 获取标签类型
     */
    public abstract TagType getType();
    
    /**
     * 解析标签属性 如果是单标签则需要设置标签结束,不包括快标签的结束标签
     */
    public abstract boolean parseTag(StatementParser parser) throws StatementException;
    
    /**
     * 是否支持多表达式执行，@call 表达式支持
     */
    protected boolean isMultiExpression() {
        return false;
    }
    
    /**
     * 解析表达式
     */
    protected void parseExpression(int begin_line, int begin_column, StringBuilder expression) throws
                                                                                               StatementException {
        try {
            ExpressionParser p = new ExpressionParser(begin_line, begin_column, expression, this.getPath());
            ArrayList<Expression> ls = p.parse();
            if (ls == null) {ls = new ArrayList<Expression>(0);}
            
            ls.trimToSize();
            this.expressions = ls;
            
            // 判断是否支持多表达式执行
            if (!this.isMultiExpression() && ls.size() > 1) {
                throw new ExpressionException(ls.get(0), "表达式结果不正确，若想返回多个结果，请使用[]方式");
            }
        }
        catch (ExpressionException e) {
            String path = this.getPath(), msg = this + " " + e.getCusomMessage();
            int line = e.getLine() < begin_line ? begin_line : e.getLine(), col = e.getColumn();
            throw new StatementException(path, line, col, msg, e);
        }
    }
    
    /**
     * 提供公开的调用方法
     **/
    public void execute(VariableMap variableMap, PrintWriter out) {
        try {
            this.build(variableMap, out);
        }
        catch (Throwable e) {
            // 标签异常
            // 表达式异常
            // ContinueException 继续循环
            // BreakException 退出循环
            // ReturnException 返回
            if (e instanceof StatementException || e instanceof ExpressionException || e instanceof ContinueException || e instanceof BreakException || e instanceof ReturnException) {
                throw e;
            }
            
            String path = this.getPath();
            int line = this.getLineNo();
            int column = this.getBeginColumn();
            throw new StatementException(path, line, column, e.getMessage(), e);
        }
    }
    
}
